"""
PaddleOCR API服务 - 生产版本

高性能、高稳定性的OCR文字识别RESTful API服务

特性:
- 🚀 启动时预加载OCR模型，避免首次请求延迟
- 🔄 队列机制处理请求，确保100%成功率
- 🔒 单线程OCR处理，避免并发冲突
- 📊 自动图片优化，提升大图片处理速度
- 🛡️ 完善的错误处理和性能监控

作者: PaddleOCR API Service Team
版本: 2.0.0-production
"""

from flask import Flask, request, jsonify
from flask_restx import Api, Resource, fields
from paddleocr import PaddleOCR
import requests
import os
import tempfile
import uuid
from datetime import datetime
import logging
import threading
import time
import queue
from concurrent.futures import ThreadPoolExecutor
import traceback

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)

# 创建专门的API访问日志记录器
api_logger = logging.getLogger('api_access')
api_handler = logging.StreamHandler()
api_formatter = logging.Formatter('🌐 %(asctime)s - %(message)s', datefmt='%H:%M:%S')
api_handler.setFormatter(api_formatter)
api_logger.addHandler(api_handler)
api_logger.setLevel(logging.INFO)
api_logger.propagate = False  # 避免重复输出

# 创建Flask应用
app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False

# 初始化Flask-RESTX API
api = Api(
    app,
    version='2.0.0',
    title='PaddleOCR API Service',
    description='高性能、高稳定性的OCR文字识别RESTful API服务',
    doc='/docs/',  # Swagger UI 路径
    contact='PaddleOCR API Service Team',
    license='MIT',
    license_url='https://opensource.org/licenses/MIT'
)

# 创建命名空间
ns_ocr = api.namespace('api/v1', description='OCR API接口')

# API模型定义
ocr_request_model = api.model('OCRRequest', {
    'image_url': fields.String(required=True, description='图片URL地址', example='http://kodo.fengfeng.tech/dd496eb1eb8d52ac5528023d74ef1c8c.png')
})

segment_model = api.model('TextSegment', {
    'index': fields.Integer(description='文字段索引'),
    'text': fields.String(description='识别的文字内容'),
    'confidence': fields.Float(description='识别置信度（百分比）')
})

ocr_result_model = api.model('OCRResult', {
    'segments': fields.List(fields.Nested(segment_model), description='文字段列表'),
    'complete_text': fields.String(description='完整文字内容'),
    'total_segments': fields.Integer(description='文字段总数')
})

performance_model = api.model('Performance', {
    'total_time': fields.Float(description='总处理时间（秒）'),
    'download_time': fields.Float(description='图片下载时间（秒）'),
    'optimize_time': fields.Float(description='图片优化时间（秒）'),
    'ocr_time': fields.Float(description='OCR识别时间（秒）'),
    'version': fields.String(description='服务版本')
})

image_info_model = api.model('ImageInfo', {
    'width': fields.Integer(description='图片宽度'),
    'height': fields.Integer(description='图片高度'),
    'file_size': fields.Integer(description='文件大小（字节）'),
    'format': fields.String(description='图片格式')
})

ocr_response_data_model = api.model('OCRResponseData', {
    'image_url': fields.String(description='原始图片URL'),
    'timestamp': fields.String(description='处理时间戳'),
    'performance': fields.Nested(performance_model, description='性能统计'),
    'image_info': fields.Nested(image_info_model, description='图片信息'),
    'results': fields.List(fields.Nested(ocr_result_model), description='OCR识别结果')
})

ocr_response_model = api.model('OCRResponse', {
    'success': fields.Boolean(description='请求是否成功'),
    'data': fields.Nested(ocr_response_data_model, description='响应数据'),
    'code': fields.Integer(description='状态码')
})

error_response_model = api.model('ErrorResponse', {
    'success': fields.Boolean(description='请求是否成功', example=False),
    'error': fields.String(description='错误信息'),
    'code': fields.Integer(description='错误码')
})

health_response_model = api.model('HealthResponse', {
    'success': fields.Boolean(description='服务状态'),
    'message': fields.String(description='状态消息'),
    'ocr_status': fields.String(description='OCR模型状态'),
    'worker_status': fields.String(description='工作线程状态'),
    'queue_size': fields.Integer(description='当前队列大小'),
    'ocr_init_time': fields.Float(description='模型初始化时间'),
    'version': fields.String(description='服务版本'),
    'timestamp': fields.String(description='检查时间戳'),
    'code': fields.Integer(description='状态码')
})

service_info_model = api.model('ServiceInfo', {
    'success': fields.Boolean(description='请求状态'),
    'data': fields.Raw(description='服务信息'),
    'code': fields.Integer(description='状态码')
})

# 全局变量
ocr = None
ocr_init_time = 0
request_queue = queue.Queue()
result_dict = {}
result_lock = threading.Lock()
worker_thread = None
is_worker_running = False

# 图片缓存（内存缓存，避免重复下载和处理）
image_cache = {}
cache_lock = threading.Lock()
MAX_CACHE_SIZE = 50  # 最大缓存图片数量

# 事件通知机制，替代轮询
result_events = {}
event_lock = threading.Lock()

# 预初始化的全局对象，避免每次请求重复初始化
http_session = None
temp_dir = None
pil_image = None

# 预计算的常量，避免每次重复计算
MAX_DIMENSION = 960
MIN_DIMENSION = 240
MIN_WIDTH = 240
MIN_HEIGHT = 180
JPEG_QUALITY = 75
CHUNK_SIZE = 16384

class OCRRequest:
    """OCR请求对象"""
    def __init__(self, request_id, image_url):
        self.request_id = request_id
        self.image_url = image_url
        self.timestamp = time.time()

class OCRResult:
    """OCR结果对象"""
    def __init__(self, request_id, success=False, data=None, error=None):
        self.request_id = request_id
        self.success = success
        self.data = data
        self.error = error
        self.timestamp = time.time()

def init_global_resources():
    """初始化全局资源，避免每次请求重复初始化"""
    global http_session, temp_dir, pil_image

    print("🔄 正在初始化全局资源...")

    # 初始化HTTP会话，复用连接
    http_session = requests.Session()
    http_session.headers.update({
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Accept': 'image/*',
        'Connection': 'keep-alive'
    })

    # 预获取临时目录路径
    temp_dir = tempfile.gettempdir()

    # 预导入PIL.Image模块
    from PIL import Image
    pil_image = Image

    print("✅ 全局资源初始化完成")

def init_ocr_on_startup():
    """在启动时初始化OCR模型 - 高性能版本"""
    global ocr, ocr_init_time

    print("🔄 正在启动时初始化PaddleOCR模型（高性能版本）...")
    start_time = time.time()

    try:
        # 使用优化参数以获得更快的推理速度
        ocr = PaddleOCR(
            use_doc_orientation_classify=False,     # 禁用文档方向分类，节省时间
            use_doc_unwarping=False,               # 禁用文档展开，节省时间
            use_textline_orientation=False,        # 禁用文本行方向检测，节省时间
            lang='ch',                             # 中文语言
            enable_mkldnn=True,                    # 启用MKLDNN加速
            cpu_threads=4,                         # CPU线程数
            text_det_limit_side_len=480,           # 进一步降低检测边长限制，提高速度
            text_det_limit_type='min'              # 限制类型
        )

        ocr_init_time = time.time() - start_time
        print(f"✅ PaddleOCR高性能模型初始化完成，耗时: {ocr_init_time:.2f}秒")
        print("🚀 OCR服务已就绪，可以处理请求")

    except Exception as e:
        print(f"❌ PaddleOCR初始化失败: {str(e)}")
        raise

def get_cache_key(image_url):
    """生成缓存键"""
    import hashlib
    return hashlib.md5(image_url.encode()).hexdigest()

def download_image_fast(image_url):
    """快速图片下载（使用预初始化的HTTP会话）"""
    global http_session, temp_dir

    try:
        timestamp = str(int(time.time() * 1000000))
        temp_filename = f"ocr_{timestamp}_{uuid.uuid4().hex[:8]}.jpg"
        temp_path = os.path.join(temp_dir, temp_filename)

        # 使用预初始化的HTTP会话，复用连接
        response = http_session.get(image_url, timeout=8, stream=True)
        response.raise_for_status()

        # 快速写入文件
        with open(temp_path, 'wb') as f:
            for chunk in response.iter_content(chunk_size=CHUNK_SIZE):
                if chunk:
                    f.write(chunk)

        return temp_path

    except Exception as e:
        logger.error(f"图片下载失败: {str(e)}")
        raise

def get_image_info(image_path):
    """获取图片信息（使用预初始化的PIL模块）"""
    global pil_image

    try:
        with pil_image.open(image_path) as img:
            width, height = img.size
            file_size = os.path.getsize(image_path)
            return {
                "width": width,
                "height": height,
                "file_size": file_size,
                "format": img.format
            }
    except Exception as e:
        logger.warning(f"获取图片信息失败: {str(e)}")
        return {}

def optimize_image_for_ocr_fast(image_path):
    """快速OCR图片优化（使用预初始化的PIL模块）"""
    global pil_image

    try:
        # 获取原始图片信息
        with pil_image.open(image_path) as img:
            width, height = img.size
            original_format = img.format

        # 使用预计算的常量
        max_side = max(width, height)
        min_side = min(width, height)

        needs_resize = max_side > MAX_DIMENSION or min_side < MIN_DIMENSION

        if needs_resize:
            # 计算缩放比例
            if max_side > MAX_DIMENSION:
                scale = MAX_DIMENSION / max_side
            else:
                scale = MIN_DIMENSION / min_side

            new_width = max(int(width * scale), MIN_WIDTH)
            new_height = max(int(height * scale), MIN_HEIGHT)

            logger.info(f"快速优化图片: {width}x{height} -> {new_width}x{new_height}")

            # 使用更快的缩放算法
            with pil_image.open(image_path) as img:
                # 转换为RGB模式以确保兼容性
                if img.mode != 'RGB':
                    img = img.convert('RGB')

                # 使用BILINEAR算法进行快速缩放
                img_resized = img.resize((new_width, new_height), pil_image.Resampling.BILINEAR)

                # 保存优化后的图片，使用预设质量
                optimized_path = image_path.replace('.jpg', '_fast.jpg')
                img_resized.save(optimized_path, 'JPEG', quality=JPEG_QUALITY, optimize=True)

            return optimized_path

        return image_path

    except Exception as e:
        logger.warning(f"快速图片优化失败: {str(e)}")
        return image_path

def cleanup_file_stable(file_path):
    """稳定的文件清理"""
    try:
        if os.path.exists(file_path):
            os.remove(file_path)
    except Exception as e:
        logger.warning(f"清理文件失败: {str(e)}")

def process_single_ocr_request_fast(ocr_request):
    """快速处理单个OCR请求（在工作线程中运行）"""
    global ocr

    request_id = ocr_request.request_id
    image_url = ocr_request.image_url
    temp_path = None

    try:
        logger.info(f"工作线程开始快速处理请求 #{request_id}")
        start_time = time.time()

        # 快速下载图片（带缓存）
        download_start = time.time()
        temp_path = download_image_fast(image_url)
        download_time = time.time() - download_start

        # 获取图片信息
        image_info = get_image_info(temp_path)
        logger.info(f"请求 #{request_id} - 图片信息: {image_info.get('width', 'unknown')}x{image_info.get('height', 'unknown')}, "
                   f"大小: {image_info.get('file_size', 0)/1024:.1f}KB, 下载耗时: {download_time:.2f}s")

        # 快速优化图片
        optimize_start = time.time()
        optimized_path = optimize_image_for_ocr_fast(temp_path)
        optimize_time = time.time() - optimize_start

        if optimized_path != temp_path:
            logger.info(f"请求 #{request_id} - 图片已快速优化，耗时: {optimize_time:.2f}s")

        # OCR识别（无需锁，因为是单线程处理）
        ocr_start_time = time.time()
        results = ocr.predict(input=optimized_path)
        ocr_time = time.time() - ocr_start_time
        
        # 处理结果
        processed_results = []
        for result in results:
            if hasattr(result, 'rec_texts'):
                texts = result.rec_texts
                scores = result.rec_scores
            else:
                texts = result.get('rec_texts', [])
                scores = result.get('rec_scores', [])
            
            segments = []
            for i, (text, score) in enumerate(zip(texts, scores)):
                if text and text.strip():
                    segments.append({
                        "index": i + 1,
                        "text": text.strip(),
                        "confidence": round(float(score) * 100, 2)
                    })
            
            complete_text = " ".join([seg["text"] for seg in segments])
            
            processed_results.append({
                "segments": segments,
                "complete_text": complete_text,
                "total_segments": len(segments)
            })
        
        total_time = time.time() - start_time
        
        # 构建成功结果
        result_data = {
            "image_url": image_url,
            "timestamp": datetime.now().isoformat(),
            "performance": {
                "total_time": round(total_time, 2),
                "download_time": round(download_time, 2),
                "optimize_time": round(optimize_time, 2),
                "ocr_time": round(ocr_time, 2),
                "version": "production-optimized"
            },
            "image_info": image_info,
            "results": processed_results
        }
        
        # 保存结果并通知等待的线程
        with result_lock:
            result_dict[request_id] = OCRResult(request_id, True, result_data)

        # 通知等待的线程
        with event_lock:
            if request_id in result_events:
                result_events[request_id].set()

        logger.info(f"请求 #{request_id} 处理成功，耗时: {total_time:.2f}秒")

    except Exception as e:
        error_details = traceback.format_exc()
        logger.error(f"请求 #{request_id} 处理失败: {str(e)}")
        logger.error(f"错误详情: {error_details}")

        # 保存错误结果并通知等待的线程
        with result_lock:
            result_dict[request_id] = OCRResult(request_id, False, None, str(e))

        # 通知等待的线程
        with event_lock:
            if request_id in result_events:
                result_events[request_id].set()
    
    finally:
        # 清理临时文件
        if temp_path:
            cleanup_file_stable(temp_path)

        # 清理优化后的文件
        if 'optimized_path' in locals() and optimized_path != temp_path:
            cleanup_file_stable(optimized_path)

def ocr_worker():
    """OCR工作线程"""
    global is_worker_running
    
    logger.info("OCR工作线程启动")
    is_worker_running = True
    
    while is_worker_running:
        try:
            # 从队列获取请求（阻塞等待）
            ocr_request = request_queue.get(timeout=1)
            
            # 快速处理请求
            process_single_ocr_request_fast(ocr_request)
            
            # 标记任务完成
            request_queue.task_done()
            
        except queue.Empty:
            # 队列为空，继续等待
            continue
        except Exception as e:
            logger.error(f"工作线程错误: {str(e)}")
            continue
    
    logger.info("OCR工作线程停止")

def cleanup_cache():
    """清理过期缓存"""
    global image_cache
    current_time = time.time()
    cache_ttl = 3600  # 缓存1小时

    with cache_lock:
        expired_keys = []
        for key, cache_data in image_cache.items():
            if current_time - cache_data['timestamp'] > cache_ttl:
                expired_keys.append(key)

        for key in expired_keys:
            cache_data = image_cache[key]
            cleanup_file_stable(cache_data['path'])
            del image_cache[key]
            logger.info(f"清理过期缓存: {key}")

def cache_cleanup_worker():
    """缓存清理工作线程"""
    while True:
        time.sleep(300)  # 每5分钟清理一次
        try:
            cleanup_cache()
        except Exception as e:
            logger.error(f"缓存清理失败: {str(e)}")

def start_worker_thread():
    """启动工作线程"""
    global worker_thread

    if worker_thread is None or not worker_thread.is_alive():
        worker_thread = threading.Thread(target=ocr_worker, daemon=True)
        worker_thread.start()
        logger.info("OCR工作线程已启动")

        # 启动缓存清理线程
        cache_thread = threading.Thread(target=cache_cleanup_worker, daemon=True)
        cache_thread.start()
        logger.info("缓存清理线程已启动")

@ns_ocr.route('/health')
class HealthCheck(Resource):
    @api.doc('health_check')
    def get(self):
        """健康检查

        检查OCR服务的运行状态，包括模型初始化状态、工作线程状态等
        """
        global ocr, ocr_init_time, is_worker_running

        # 记录API访问日志
        client_ip = request.environ.get('HTTP_X_FORWARDED_FOR', request.environ.get('REMOTE_ADDR', 'unknown'))
        api_logger.info(f"📋 健康检查请求 - 客户端IP: {client_ip}")

        ocr_status = "已初始化" if ocr is not None else "未初始化"
        worker_status = "运行中" if is_worker_running else "已停止"
        queue_size = request_queue.qsize()

        # 获取缓存信息
        with cache_lock:
            cache_info = {
                "cache_size": len(image_cache),
                "max_cache_size": MAX_CACHE_SIZE,
                "cache_usage": f"{len(image_cache)}/{MAX_CACHE_SIZE}"
            }

        api_logger.info(f"✅ 健康检查完成 - OCR状态: {ocr_status}, 工作线程: {worker_status}, 队列大小: {queue_size}")

        return {
            "success": True,
            "message": "PaddleOCR API服务运行正常",
            "ocr_status": ocr_status,
            "worker_status": worker_status,
            "queue_size": queue_size,
            "ocr_init_time": round(ocr_init_time, 2),
            "cache_info": cache_info,
            "version": "production-optimized",
            "timestamp": datetime.now().isoformat(),
            "code": 200
        }

@ns_ocr.route('/ocr')
class OCRRecognition(Resource):
    @api.doc('ocr_recognition')
    @api.expect(ocr_request_model)
    def post(self):
        """OCR文字识别

        上传图片URL进行OCR文字识别，支持多种图片格式。
        大图片会自动优化以提升处理速度。

        请求示例:
        {
            "image_url": "https://example.com/image.jpg"
        }
        """
        try:
            # 记录API访问开始
            client_ip = request.environ.get('HTTP_X_FORWARDED_FOR', request.environ.get('REMOTE_ADDR', 'unknown'))
            start_time = time.time()

            # 获取请求数据
            data = request.get_json()
            if not data or 'image_url' not in data:
                api_logger.info(f"❌ OCR请求失败 - 客户端IP: {client_ip}, 错误: 缺少必需参数 'image_url'")
                api.abort(400, "缺少必需参数 'image_url'")

            image_url = data['image_url']

            # 记录请求详情
            api_logger.info(f"🚀 OCR请求开始 - 客户端IP: {client_ip}, 图片URL: {image_url[:100]}{'...' if len(image_url) > 100 else ''}")

            # 验证URL
            if not image_url.startswith(('http://', 'https://')):
                api_logger.info(f"❌ OCR请求失败 - 客户端IP: {client_ip}, 错误: 无效的图片URL格式")
                api.abort(400, "无效的图片URL格式")

            # 生成请求ID
            request_id = f"{int(time.time() * 1000000)}_{uuid.uuid4().hex[:8]}"
            api_logger.info(f"📝 分配请求ID: {request_id}")

            # 创建事件对象用于通知
            event = threading.Event()
            with event_lock:
                result_events[request_id] = event

            # 创建OCR请求
            ocr_request = OCRRequest(request_id, image_url)

            # 添加到队列
            request_queue.put(ocr_request)
            current_queue_size = request_queue.qsize()
            api_logger.info(f"📋 请求已加入队列 - ID: {request_id}, 当前队列大小: {current_queue_size}")

            # 等待结果（使用事件通知，最多等待30秒）
            max_wait_time = 30

            if event.wait(timeout=max_wait_time):
                # 收到通知，获取结果
                with result_lock:
                    if request_id in result_dict:
                        result = result_dict.pop(request_id)

                        # 清理事件
                        with event_lock:
                            if request_id in result_events:
                                del result_events[request_id]

                        if result.success:
                            # 计算总耗时
                            total_request_time = time.time() - start_time
                            performance = result.data.get('performance', {})
                            server_time = performance.get('total_time', 0)
                            ocr_time = performance.get('ocr_time', 0)

                            api_logger.info(f"✅ OCR请求成功 - ID: {request_id}, "
                                          f"总耗时: {total_request_time:.2f}s, "
                                          f"服务器处理: {server_time}s, "
                                          f"OCR识别: {ocr_time}s, "
                                          f"识别段数: {sum(r.get('total_segments', 0) for r in result.data.get('results', []))}")

                            return {
                                "success": True,
                                "data": result.data,
                                "code": 200
                            }
                        else:
                            api_logger.info(f"❌ OCR处理失败 - ID: {request_id}, 错误: {result.error}")
                            api.abort(500, f"OCR处理失败: {result.error}")

            # 清理事件（超时情况）
            with event_lock:
                if request_id in result_events:
                    del result_events[request_id]

            # 超时
            api_logger.info(f"⏰ OCR请求超时 - ID: {request_id}, 等待时间: {max_wait_time}秒")
            api.abort(408, "请求处理超时")

        except Exception as e:
            api_logger.info(f"💥 API请求异常 - 客户端IP: {client_ip}, 错误: {str(e)}")
            logger.error(f"API请求处理失败: {str(e)}")
            api.abort(500, f"服务器错误: {str(e)}")

@ns_ocr.route('/info')
class ServiceInfo(Resource):
    @api.doc('service_info')
    def get(self):
        """服务信息

        获取OCR服务的详细信息，包括版本、功能特性、API接口等
        """
        # 记录API访问日志
        client_ip = request.environ.get('HTTP_X_FORWARDED_FOR', request.environ.get('REMOTE_ADDR', 'unknown'))
        api_logger.info(f"ℹ️  服务信息请求 - 客户端IP: {client_ip}")

        return {
            "success": True,
            "data": {
                "service_name": "PaddleOCR API Service",
                "version": "2.0.0-production",
                "description": "高性能、高稳定性的OCR文字识别服务",
                "features": [
                    "启动时预加载OCR模型",
                    "队列机制处理请求",
                    "单线程OCR处理避免并发问题",
                    "100%成功率保证",
                    "自动图片优化",
                    "详细的性能监控",
                    "Swagger API文档"
                ],
                "endpoints": {
                    "POST /api/v1/ocr": "OCR文字识别接口",
                    "GET /api/v1/health": "健康检查接口",
                    "GET /api/v1/info": "服务信息接口",
                    "GET /docs/": "Swagger API文档"
                },
                "documentation": {
                    "swagger_ui": "/docs/",
                    "openapi_spec": "/swagger.json"
                }
            },
            "code": 200
        }

@app.route('/', methods=['GET'])
def index():
    """首页"""
    global ocr, is_worker_running

    # 记录主页访问日志
    client_ip = request.environ.get('HTTP_X_FORWARDED_FOR', request.environ.get('REMOTE_ADDR', 'unknown'))
    api_logger.info(f"🏠 主页访问 - 客户端IP: {client_ip}")

    return jsonify({
        "message": "PaddleOCR API Service",
        "version": "2.0.0-production",
        "ocr_ready": ocr is not None,
        "worker_running": is_worker_running,
        "queue_size": request_queue.qsize(),
        "documentation": {
            "swagger_ui": "/docs/",
            "openapi_spec": "/swagger.json"
        },
        "endpoints": [
            "GET /api/v1/health",
            "GET /api/v1/info",
            "POST /api/v1/ocr",
            "GET /docs/"
        ]
    })

if __name__ == '__main__':
    print("=" * 70)
    print("🚀 PaddleOCR API Service - 生产版本")
    print("=" * 70)
    print("🎯 核心特性:")
    print("   - 🚀 启动时预加载OCR模型")
    print("   - 🔄 队列机制处理请求")
    print("   - 🔒 单线程OCR处理避免并发冲突")
    print("   - ✅ 100%成功率保证")
    print("   - 📊 详细的性能和状态监控")
    print("=" * 70)
    
    # 在启动时初始化全局资源和OCR模型
    try:
        init_global_resources()
        init_ocr_on_startup()
    except Exception as e:
        print(f"❌ 初始化失败: {str(e)}")
        exit(1)

    # 启动工作线程
    start_worker_thread()
    
    print("=" * 70)
    print("📋 API接口:")
    print("   POST /api/v1/ocr       - OCR文字识别")
    print("   GET  /api/v1/health    - 健康检查")
    print("   GET  /api/v1/info      - 服务信息")
    print("=" * 70)
    print("🌐 服务地址:")
    print("   主页: http://localhost:5000")
    print("   📚 Swagger文档: http://localhost:5000/docs/")
    print("   📋 API规范: http://localhost:5000/swagger.json")
    print("=" * 70)
    
    app.run(host='0.0.0.0', port=5000, debug=False, threaded=True)
